﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices;
using System.Security.Principal;

namespace FixMailboxSD
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length < 1 || args.Length > 2)
            {
                DisplaySyntax();
                return;
            }

            string dn = "";
            bool removeMasterAccount = false;

            foreach (string arg in args)
            {
                if (arg.StartsWith("-") || arg.StartsWith("/"))
                {
                    if (arg.ToLower() == "-removemasteraccount" || arg.ToLower() == "/removemasteraccount")
                    {
                        removeMasterAccount = true;
                    }
                    else
                    {
                        DisplaySyntax();
                        return;
                    }
                }
                else
                {
                    dn = arg;
                }
            }

            DirectoryEntry entry = null;
            string masterAccountName = "";
            string masterAccountSidString = "";
            try
            {
                entry = new DirectoryEntry("LDAP://" + dn);
                string objectClass = entry.Properties["objectClass"][0].ToString();
                if (entry.Properties.Contains("msExchMasterAccountSid"))
                {
                    byte[] sidBytes = (byte[])entry.Properties["msExchMasterAccountSid"][0];
                    SecurityIdentifier masterAccountSid = new SecurityIdentifier(sidBytes, 0);
                    masterAccountSidString = masterAccountSid.ToString();
                    try
                    {
                        masterAccountName = ((NTAccount)masterAccountSid.Translate(typeof(NTAccount))).ToString();
                    }
                    catch
                    {
                    }
                }
            }
            catch (Exception exc)
            {
                Console.WriteLine("Could not bind to DN. Exception: " + exc.Message);
                return;
            }

            List<ActiveDs.IADsAccessControlEntry> allow = new List<ActiveDs.IADsAccessControlEntry>();
            List<ActiveDs.IADsAccessControlEntry> deny = new List<ActiveDs.IADsAccessControlEntry>();
            List<ActiveDs.IADsAccessControlEntry> allowObject = new List<ActiveDs.IADsAccessControlEntry>();
            List<ActiveDs.IADsAccessControlEntry> denyObject = new List<ActiveDs.IADsAccessControlEntry>();
            List<ActiveDs.IADsAccessControlEntry> inherited = new List<ActiveDs.IADsAccessControlEntry>();

            CDOEXM.IExchangeMailbox mbx = (CDOEXM.IExchangeMailbox)entry.NativeObject;
            ActiveDs.IADsSecurityDescriptor iadsSD = (ActiveDs.IADsSecurityDescriptor)mbx.MailboxRights;
            ActiveDs.AccessControlList dacl = (ActiveDs.AccessControlList)iadsSD.DiscretionaryAcl;

            Console.WriteLine("Ace count: " + dacl.AceCount.ToString());
            Console.WriteLine("Current DACL:");
            foreach(ActiveDs.IADsAccessControlEntry ace in dacl)
            {
                WriteAce(ace);

                if (removeMasterAccount)
                {
                    if (ace.Trustee == masterAccountName || ace.Trustee == masterAccountSidString)
                    {
                        // We're going to skip this ACE so it's removed from the DACL. That
                        // means it's also time to clear the attribute.
                        entry.Properties["msExchMasterAccountSid"].Clear();
                        entry.CommitChanges();
                        continue;
                    }
                }

                if (ace.Trustee.StartsWith("S-1"))
                {
                    // This is an unresolved SID. We want to remove these, so
                    // we're skipping it. However, if it's in msExchMasterAccountSID,
                    // we need to remove it there, too.
                    if (ace.Trustee == masterAccountSidString)
                    {
                        entry.Properties["msExchMasterAccountSid"].Clear();
                        entry.CommitChanges();
                    }
                }
                else if ((ace.AceFlags & 0x10) == 0x10)
                {
                    inherited.Add(ace);
                }
                else
                {
                    if (ace.AceType == (int)ActiveDs.ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_ALLOWED)
                    {
                        allow.Add(ace);
                    }
                    else if (ace.AceType == (int)ActiveDs.ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_DENIED)
                    {
                        deny.Add(ace);
                    }
                    else if (ace.AceType == (int)ActiveDs.ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_ALLOWED_OBJECT)
                    {
                        allowObject.Add(ace);
                    }
                    else if (ace.AceType == (int)ActiveDs.ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_DENIED_OBJECT)
                    {
                        denyObject.Add(ace);
                    }
                    else
                    {
                        throw new Exception("Cannot canonicalize the DACL because it contains an unhandled ACE type.");
                    }
                }
            }

            foreach (ActiveDs.IADsAccessControlEntry ace in dacl)
            {
                dacl.RemoveAce(ace);
            }

            foreach (ActiveDs.IADsAccessControlEntry ace in deny)
                dacl.AddAce(ace);
            foreach (ActiveDs.IADsAccessControlEntry ace in denyObject)
                dacl.AddAce(ace);
            foreach (ActiveDs.IADsAccessControlEntry ace in allow)
                dacl.AddAce(ace);
            foreach (ActiveDs.IADsAccessControlEntry ace in allowObject)
                dacl.AddAce(ace);
            foreach (ActiveDs.IADsAccessControlEntry ace in inherited)
                dacl.AddAce(ace);

            Console.WriteLine("New DACL:");
            foreach (ActiveDs.IADsAccessControlEntry ace in dacl)
            {
                WriteAce(ace);
            }

            iadsSD.DiscretionaryAcl = dacl;
            mbx.MailboxRights = iadsSD;
            entry.CommitChanges();
        }

        private static void DisplaySyntax()
        {
                            Console.WriteLine("Syntax:");
                Console.WriteLine("FixMailboxSD <distinguishedName of mailbox>");
                Console.WriteLine("Example:");
                Console.WriteLine(@"FixMailboxSD ""CN=Test 1,OU=Mailboxes,DC=contoso,DC=com""");
        }

        private static void WriteAce(ActiveDs.IADsAccessControlEntry ace)
        {
            StringBuilder aceString = new StringBuilder();
            if ((ace.AceFlags & 0x10) == 0x10)
                aceString.Append("     Inherited");
            else
                aceString.Append("     Explicit ");

            if (ace.AceType == (int)ActiveDs.ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_ALLOWED)
                aceString.Append("     Allow      ");
            else if (ace.AceType == (int)ActiveDs.ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_DENIED)
                aceString.Append("     Deny       ");
            else if (ace.AceType == (int)ActiveDs.ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_ALLOWED_OBJECT)
                aceString.Append("     AllowObject");
            else if (ace.AceType == (int)ActiveDs.ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_DENIED_OBJECT)
                aceString.Append("     DenyObject ");

            aceString.Append(ace.Trustee);

            Console.WriteLine(aceString.ToString());
        }
    }
}
